package de.akabahn.ebd.stw.zdxfc.prs.elemente;

import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT0;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT10;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT2;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT4;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT6;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.BIT8;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.GLEISFREIMELDUNG_FREI;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.LAGE;
import static de.akabahn.ebd.stw.zdxfc.prs.ZDXFCConstants.WEICHE;

import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;

import de.akabahn.ebd.comm.dbd.DBDParameter;
import de.akabahn.ebd.dbd.SwitchPosition;
import de.akabahn.ebd.stw.zdxfc.prs.ZDXFCMain;

/**
 * Implementierung des Fahrwegelements <code>Weiche</code>.<br>
 * <br>
 * Bitcodierung des Weichen-Gleismelders (weichenMelder[0])<br>
 * <pre>
 * BIT11 BIT10 BIT9 BIT8 BIT7 BIT6 BIT5 BIT4 BIT3 BIT2 BIT1 BIT0  BEDEUTUNG 
 *     0     0    0    0    0    0    0    0    0    0    0    0  AUS
 *     0     0    0    0    0    0    0    0    0    0    0    1  SPITZE WEISS AN
 *     0     0    0    0    0    0    0    0    0    0    1    0  SPITZE WEISS BLINKT
 *     0     0    0    0    0    0    0    0    0    1    0    0  SPITZE ROT AN
 *     0     0    0    0    0    0    0    0    1    0    0    0  SPITZE ROT BLINKT
 *     0     0    0    0    0    0    0    1    0    0    0    0  RECHTS WEISS AN
 *     0     0    0    0    0    0    1    0    0    0    0    0  RECHTS WEISS BLINKT
 *     0     0    0    0    0    1    0    0    0    0    0    0  RECHTS ROT AN
 *     0     0    0    0    1    0    0    0    0    0    0    0  RECHTS ROT BLINKT
 *     0     0    0    1    0    0    0    0    0    0    0    0  LINKS WEISS AN
 *     0     0    1    0    0    0    0    0    0    0    0    0  LINKS WEISS BLINKT
 *     0     1    0    0    0    0    0    0    0    0    0    0  LINKS ROT AN
 *     1     0    0    0    0    0    0    0    0    0    0    0  LINKS ROT BLINKT
 * </pre>
 * <br>
 * Bitcodierung des Weichen-Lagemelders (weichenMelder[1])<br>
 * <pre>
 * BIT3 BIT2 BIT1 BIT0  BEDEUTUNG
 *    0    0    0    0  AUS
 *    0    0    0    1  RECHTS WEISS AN
 *    0    0    1    0  RECHTS WEISS BLINKT
 *    0    1    0    0  LINKS WEISS AN
 *    1    0    0    0  LINKS WEISS BLINKT
 * </pre>
 * 
 * @author Peter Eimann
 * @since 1.0
 * @version 1.2
 */
public class Weiche extends FahrwegElement {

	private final static Logger LOG = Logger.getLogger(Weiche.class);
	
	private CountDownLatch latch;
	private SwitchPosition sollLage;
	private SwitchPosition istLage;
	private boolean besetzt;
	private boolean flankenSchutz;
	private boolean zeigeLageMelder;
	private boolean zeigeGleisMelder;
	private String[] weichenMelder;

	/**
	 * Initialisierung der Klasse
	 * 
	 * @param id
	 *            Eindeutige ID des Elements, wird fuer die Referenz auf das
	 *            Element benoetigt (die ID ist der Schluessel in den Maps, in
	 *            denen das Element gespeichert ist.
	 * @param weichenMelder
	 *            Name der DBD-Variablen fuer den der Weiche zugeordneten Gleis-
	 *            (Position 0) und Lagemelder (Position 1) im TCO
	 * @param main
	 *            Referenz auf die Hauptklasse
	 */
	public Weiche(String id, String[] weichenMelder, ZDXFCMain main) {
		super(id, main);
		this.weichenMelder = weichenMelder;
	}

	/**
	 * Gibt die aktuelle Istlage (rechts / links) der Weiche zurueck
	 * 
	 * @return Die Istlage der Weiche
	 */
	public SwitchPosition getIstLage() {
		return istLage;
	}

	/**
	 * Setzt die neue Istlage der Weiche (diese Methode schreibt den Begriff nicht in die entsprechende
	 * DBD-Variable, dafuer muss <code>umstellen(SwitchPosition, CountDownLatch)</code> aufgerufen werden).<br>
	 * Ist die Weichenausleuchtung eingeschaltet, wird <code>lageMelderAusleuchtung()</code> aufgerufen.
	 * 
	 * @param istLage Die neue Istlage der Weiche
	 * @see #umstellen(SwitchPosition, CountDownLatch)
	 * @see #lageMelderAusleuchtung()
	 */
	public void setIstLage(SwitchPosition istLage) {
		this.istLage = istLage;
		
		if (zeigeLageMelder) {
			lageMelderAusleuchtung();
		}
		
		LOG.info(id + ": Ist-Lage = " + istLage);
	}

	/**
	 * Gibt die aktuelle Solllage (rechts / links) der Weiche zurueck
	 *  
	 * @return Die Solllage der Weiche
	 */
	public SwitchPosition getSollLage() {
		return sollLage;
	}

	/**
	 * Setzt die neue Solllage der Weiche (diese Methode schreibt den Begriff nicht in die entsprechende
	 * DBD-Variable, dafuer muss <code>umstellen(SwitchPosition, CountDownLatch)</code> aufgerufen werden).
	 * 
	 * @param sollLage Die neue Solllage der Weiche
	 * @see #umstellen(SwitchPosition, CountDownLatch)
	 */
	public void setSollLage(SwitchPosition sollLage) {
		this.sollLage = sollLage;
		LOG.info(id + ": Soll-Lage = " + sollLage);
	}

	/**
	 * Gibt den <code>CountdownLatch</code> zurueck, der der Weiche zugeordnet ist. Dieser Latch wird bei der
	 * Umstellung verwendet, um dem Aufrufer anzuzeigen, dass die Weiche nach der Umstellung Endlage erreicht hat.
	 * 
	 * @return Der CountdownLatch, der bei Endlage gesetzt wird
	 */
	public CountDownLatch getLatch() {
		return latch;
	}

	/**
	 * Setzt den <code>CountdownLatch</code>, der zur Anzeige der Endlage nach Umstellung gesetzt wird
	 * 
	 * @param latch Der CountdownLatch, der bei Endlage gesetzt wird
	 */
	public void setLatch(CountDownLatch latch) {
		this.latch = latch;
	}

	/**
	 * Gibt zurueck, ob die Weiche besetzt ist oder nicht
	 * 
	 * @return <code>true</code> - Weiche ist besetzt
	 */
	public boolean isBesetzt() {
		return besetzt;
	}

	/**
	 * Setzt das Besetzt-Flag fuer die Weiche
	 * 
	 * @param besetzt Das Besetzt-Flag
	 */
	public void setBesetzt(boolean besetzt) {
		this.besetzt = besetzt;
		LOG.info(id + ": Besetzt = " + besetzt);
	}

	/**
	 * Schaltet das <code>zeigeLageMelder</code> Flag ein oder aus, wenn die Taste <code>Test Aig.</code> auf dem 
	 * Bedienpult gedrueckt oder losgelassen wird.<br>
	 * Ist der neue Wert <code>true</code>, wird <code>lageMelderAusleuchtung()</code> aufgerufen, anderenfalls wird
	 * die Ausleuchtung geloescht.
	 * 
	 * @see #lageMelderAusleuchtung()
	 */
	public void setZeigeLageMelder(boolean zeigeLageMelder) {
		this.zeigeLageMelder = zeigeLageMelder;
		
		if (this.zeigeLageMelder) {
			lageMelderAusleuchtung();
		} else {
			main.getDbd().set(weichenMelder[LAGE], 0);
		}
		
		LOG.info(id + ": Zeige Lagemelder = " + this.zeigeLageMelder);
	}

	/**
	 * Schaltet das <code>zeigeGleisMelder</code> Flag ein oder aus, wenn die Taste <code>Test Zones</code> auf dem 
	 * Bedienpult gedrueckt oder losgelassen wird, anschliessend wird <code>gleisMelderAusleuchtung()</code> aufgerufen.
	 * 
	 * @see #gleisMelderAusleuchtung()
	 */
	public void setZeigeGleisMelder(boolean zeigeGleisMelder) {
		this.zeigeGleisMelder = zeigeGleisMelder;
		gleisMelderAusleuchtung();
		LOG.info(id + ": Zeige Gleismelder = " + this.zeigeGleisMelder);
	}

	/**
	 * Umstellung der Weiche in die uebergebene Solllage.<br>
	 * Die Weiche wird nur umgestellt, wenn die Istlage nicht der neuen Solllage entspricht und die Weiche
	 * nicht besetzt ist, nicht von einer Fahrstrasse beansprucht wird und keinen Flankenschutz bieten muss.<br>
	 * Sind diese Bedingungen erfuellt, wird die neue Solllage und der CountdownLatch gespeichert und die Solllage
	 * wird in die entsprechende DBD-Variable (ID der Weiche plus <code>S</code>) geschrieben.
	 * 
	 * @param sollLage Die neue Solllage der Weiche
	 * @param latch CountdownLatch der zur Anzeige der Endlage nach der Umstellung verwendet wird, er wird sofort
	 *              heruntergezaehlt, wenn die Weiche nicht umgestellt werden kann
	 */
	public void umstellen(SwitchPosition sollLage, CountDownLatch latch) {
		if (getIstLage() == sollLage || isBesetzt() || isAktiv() || isFlankenSchutz()) {
			latch.countDown();
		} else {
			setSollLage(sollLage);
			setLatch(latch);
			main.getDbd().set(id + "S", sollLage);
		}
	}

	/**
	 * Setzt das Aktiv-Flag des Elements
	 * 
	 * @param aktiv Das Aktiv-Flag
	 * @see #gleisMelderAusleuchtung()
	 */
	@Override
	public void setAktiv(boolean aktiv) {
		super.setAktiv(aktiv);
		gleisMelderAusleuchtung();
	}

	/**
	 * Gibt das Flag zurueck, dass angibt, ob die Weiche Flankenschutz bietet oder nicht.
	 * 
	 * @return <code>true</code> - Weiche bietet Flankenschutz
	 */
	public boolean isFlankenSchutz() {
		return flankenSchutz;
	}

	/**
	 * Setzt das Flag, dass angibt, ob die Weiche Flankenschutz bietet oder nicht.
	 * 
	 * @param flankenSchutz Das Flankenschutz-Flag
	 */
	public void setFlankenSchutz(boolean flankenSchutz) {
		this.flankenSchutz = flankenSchutz;
		LOG.info(id + ": Flankenschutz = " + flankenSchutz);
	}

	/**
	 * Endet der Name der DBD-Variablen mit <code>I</code> (Istlage):<br>
	 * <ul>
	 * <li>Speichern der neuen Istlage</li>
	 * <li>Wenn die neue Istlage der Solllage entspricht und ein CountdownLatch gesetzt ist, wird dieser Latch zur
	 * Anzeige der Endlage heruntergezaehlt</li>
	 * </ul>
	 * <br>
	 * Endet der Name der DBD-Variablen mit <code>B</code> (Besetztinformation):<br>
	 * <ul>
	 * <li>Setzen des Besetzt-Flags der Weiche</li>
	 * </ul>
	 * 
	 * @param dbd Die geaenderte Variable, ID des Namens muss mit der ID des Elements uebereinstimmen
	 * @see #setIstLage(SwitchPosition)
	 * @see #setBesetzt(boolean)
	 * @see #setAktiv(boolean)
	 * @see #gleisMelderAusleuchtung()
	 */
	@Override
	public void onChangeProcess(DBDParameter dbd) {
		if (dbd.getId().equals(id)) {
			if (dbd.getName().endsWith("I")) {
				setIstLage(SwitchPosition.fromValue(dbd.getValue()));
				if ((getLatch() != null) && (getSollLage() == getIstLage())) {
					getLatch().countDown();
				}
			} else if (dbd.getName().endsWith("B")) {
				setBesetzt(dbd.getValue() != GLEISFREIMELDUNG_FREI);
				gleisMelderAusleuchtung();
			}
		}
	}

	/**
	 * Ausgabe des Zustands der Weiche
	 */
	public String toString() {
		return id + ": IST[" + istLage + "] BES[" + besetzt + "] AKT[" + isAktiv() + "] FS[" + flankenSchutz + "]";
	}
	
	/**
	 * Leuchtet den Lagemelder der Weiche entsprechend ihrer Lage aus, wenn die Ausleuchtung durch die Taste 
	 * <code>Test Aig.</code> auf dem Bedienpult eingeschaltet wurde.
	 */
	private void lageMelderAusleuchtung() {
		if (getIstLage() == SwitchPosition.RIGHT) {
			main.getDbd().set(weichenMelder[LAGE], BIT0);
		} else if (getIstLage() == SwitchPosition.LEFT) {
			main.getDbd().set(weichenMelder[LAGE], BIT2);
		} else {
			main.getDbd().set(weichenMelder[LAGE], 0);
		}
	}
	
	/**
	 * Steuert die Ausleuchtung des Gleismelders der Weiche im TCO.<br>
	 * <ul>
	 * <li>Weiche ist aktiv und nicht besetzt: Rote Ausleuchtung des Gleismelders in der entsprechenden Lage
	 * (Spitzenmelder nur wenn das Aktiv-Flag gesetzt ist)</li>
	 * <li>Weiche ist aktiv und nicht besetzt: Weisse Ausleuchtung des Gleismelders in der entsprechenden Lage</li>
	 * <li>Weiche ist nicht aktiv und nicht besetzt: Keine Ausleuchtung</li>
	 * </ul>
	 */
	private void gleisMelderAusleuchtung() {
		if (isBesetzt()) {
			if (getIstLage() == SwitchPosition.RIGHT) {
				main.getDbd().set(weichenMelder[WEICHE], BIT6 + (isAktiv() ? BIT2 : 0));
			} else if (getIstLage() == SwitchPosition.LEFT) {
				main.getDbd().set(weichenMelder[WEICHE], BIT10 + (isAktiv() ? BIT2 : 0));
			} else {
				main.getDbd().set(weichenMelder[WEICHE], BIT6 + BIT10);
			}
		} else if (isAktiv()) {
			if (getIstLage() == SwitchPosition.RIGHT) {
				main.getDbd().set(weichenMelder[WEICHE], BIT0 + BIT4);
			} else if (getIstLage() == SwitchPosition.LEFT) {
				main.getDbd().set(weichenMelder[WEICHE], BIT0 + BIT8);
			} else {
				main.getDbd().set(weichenMelder[WEICHE], BIT0 + BIT4 + BIT8);
			}
		} else {
			if (zeigeGleisMelder) {
				if (getIstLage() == SwitchPosition.RIGHT) {
					main.getDbd().set(weichenMelder[WEICHE], BIT0 + BIT4);
				} else if (getIstLage() == SwitchPosition.LEFT) {
					main.getDbd().set(weichenMelder[WEICHE], BIT0 + BIT8);
				}
			} else {
				main.getDbd().set(weichenMelder[WEICHE], 0);
			}
		}
	}
}
